﻿using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using FluentAssertions;
using FizzWare.NBuilder;

namespace Sharp.Interprocess.Tests
{
    [TestClass]
    public class SharedHashsetTests
    {
        private const string TEST_COLLECTION_NAME = "TestCollection";

        #region Test Object Definitions

        private class TestObjectWithId
        {
            public int Id { get; set; }

            public int Num { get; set; }

            public string Str { get; set; }

            public override int GetHashCode()
            {
                int hashCode = 17;
                hashCode = 23 * hashCode + Id.GetHashCode();
                hashCode = 23 * hashCode + Num.GetHashCode();
                hashCode = 23 * hashCode + Str.GetHashCode();
                return hashCode;
            }

            public override bool Equals(object obj)
            {
                var testObj = obj as TestObjectWithId;
                return testObj != null && testObj.Id == Id;
            }
        }

        private class TestObject
        {
            public int Num { get; set; }

            public string Str { get; set; }

            public override int GetHashCode()
            {
                int hashCode = 17;
                hashCode = 23 * hashCode + Num.GetHashCode();
                hashCode = 23 * hashCode + Str.GetHashCode();
                return hashCode; 
            }

            public override bool Equals(object obj)
            {
                var testObj = obj as TestObject;
                return testObj != null && testObj.Num == Num && testObj.Str == Str;
            }

            public TestObject() { }

            public TestObject(TestObject otherObject)
            {
                Num = otherObject.Num;
                Str = otherObject.Str;
            }
        }

        #endregion

        #region Add Related Tests
        [TestMethod]
        public void SharedHashset_Add_ValueTypeData_DataAdded()
        {
            using (var sharedHashset = new SharedHashset<int>(TEST_COLLECTION_NAME, EqualityComparer<int>.Default))
            {
                sharedHashset.Add(111);
                sharedHashset.Add(122);
                sharedHashset.Add(133);

                var storedItems = sharedHashset.ToList();

                storedItems.Should().Contain(new int[] {111,122,133});
            }
        }

        [TestMethod]
        public void SharedHashset_Add_AlreadyExistingValueTypeData_DataNotAdded()
        {
            using (var sharedHashset = new SharedHashset<int>(TEST_COLLECTION_NAME))
            {
                sharedHashset.Add(111);
                var storedItemsBeforeDuplicateAdd = sharedHashset.ToList();

                sharedHashset.Add(111);
                var storedItemsAfterDuplicateAdd = sharedHashset.ToList();

                storedItemsAfterDuplicateAdd.ShouldBeEquivalentTo(storedItemsBeforeDuplicateAdd);
                sharedHashset.Should().HaveCount(1);
            }
        }
        

        [TestMethod]
        public void SharedHashset_Add_ReferenceTypeData_DataAdded()
        {
            using (var sharedHashset = new SharedHashset<TestObject>(TEST_COLLECTION_NAME))
            {
                var testItems = Builder<TestObject>.CreateListOfSize(10).Build();

                sharedHashset.AddRange(testItems);                                 
                
                var storedItems = sharedHashset.ToList();

                storedItems.ShouldBeEquivalentTo(testItems);
            }
        }

        [TestMethod]
        public void SharedHashset_Add_AlreadyExistingReferenceTypeData_DataNotAdded()
        {
            using (var sharedHashset = new SharedHashset<TestObject>(TEST_COLLECTION_NAME))
            {
                var testItem = Builder<TestObject>.CreateNew().Build();
                var duplicateTestItem = new TestObject(testItem); //copy constructor

                sharedHashset.Add(testItem);
                sharedHashset.Add(testItem);
                sharedHashset.Add(duplicateTestItem);

                sharedHashset.Should().HaveCount(1);
                sharedHashset.First().ShouldBeEquivalentTo(testItem);
            }
        }

        #endregion

        #region UnionWith Related Tests

        [TestMethod]
        public void SharedHashset_UnionWith_CollectionOfValueTypes_ItemsAddedWithoutDuplicates()
        {
            using (var sharedHashset = new SharedHashset<int>(TEST_COLLECTION_NAME))
            {
                var collection1 = new int[] { 11, 22, 33, 44, 55 };
                var collection2 = new int[] { 33, 44, 55, 66, 77 };

                var expectedCollection = collection1.Union(collection2);

                sharedHashset.AddRange(collection2);
                sharedHashset.UnionWith(collection1);

                var storedItems = sharedHashset.ToList();

                storedItems.Should().OnlyHaveUniqueItems();
                storedItems.Should().BeSubsetOf(expectedCollection);
                expectedCollection.Should().BeSubsetOf(storedItems);
            }
        }

        [TestMethod]
        public void SharedHashset_UnionWith_CollectionOfReferenceTypes_ItemsAddedWithoutDuplicates()
        {
            using (var sharedHashset = new SharedHashset<TestObject>(TEST_COLLECTION_NAME))
            {
                var expectedCollection = Builder<TestObject>.CreateListOfSize(6).Build();

                //overlapping collections
                var collection1 = expectedCollection.Take(4);
                var collection2 = expectedCollection.Skip(2).Take(4);

                sharedHashset.AddRange(collection2);
                sharedHashset.UnionWith(collection1);

                var storedItems = sharedHashset.ToList();

                storedItems.Should().OnlyHaveUniqueItems();
                storedItems.Should().BeSubsetOf(expectedCollection);
                expectedCollection.Should().BeSubsetOf(storedItems);
            }
        }

        #endregion

        #region Data Query/Fetch Tests

        [TestMethod]
        public void SharedHashset_Count_CorrectItemCountRetrieved()
        {
            using (var sharedHashset = new SharedHashset<int>(TEST_COLLECTION_NAME))
            {
                var itemsToStoreCollection = new int[] { 11, 22, 33, 44, 55 };
                sharedHashset.AddRange(itemsToStoreCollection);

                sharedHashset.Should().HaveCount(itemsToStoreCollection.Count());                
            }
        }

        [TestMethod]
        public void SharedHashset_ToList_StoredDataRetrieved()
        {
            using (var sharedHashset = new SharedHashset<int>(TEST_COLLECTION_NAME))
            {
                var itemsToStoreCollection = new int[] { 11, 22, 33, 44, 55 };
                sharedHashset.AddRange(itemsToStoreCollection);
                
                var fetchedStoredItems = sharedHashset.ToList();
                fetchedStoredItems.ShouldBeEquivalentTo(itemsToStoreCollection);
            }
        }

        [TestMethod]
        public void SharedHashset_Contains_NonExistingValueTypeData_ReturnsFalse()
        {
            using (var sharedHashset = new SharedHashset<int>(TEST_COLLECTION_NAME))
            {
                sharedHashset.Contains(1234).Should().BeFalse();
            }
        }

        [TestMethod]
        public void SharedHashset_Contains_ExistingValueTypeData_ReturnsTrue()
        {
            using (var sharedHashset = new SharedHashset<int>(TEST_COLLECTION_NAME))
            {
                sharedHashset.Add(1234);
                sharedHashset.Contains(1234).Should().BeTrue();
            }
        }

        [TestMethod]
        public void SharedHashset_Contains_NonExistingReferenceTypeData_ReturnsFalse()
        {
            using (var sharedHashset = new SharedHashset<TestObject>(TEST_COLLECTION_NAME))
            {
                var testData = Builder<TestObject>.CreateNew().Build();
                sharedHashset.Contains(testData).Should().BeFalse();
            }
        }

        [TestMethod]
        public void SharedHashset_Contains_ExistingReferenceTypeData_ReturnsTrue()
        {
            using (var sharedHashset = new SharedHashset<TestObject>(TEST_COLLECTION_NAME))
            {
                var testData = Builder<TestObject>.CreateNew().Build();
                var testDataClone = new TestObject(testData);
                sharedHashset.Add(testData);
                sharedHashset.Contains(testDataClone).Should().BeTrue();
            }
        }

        #endregion

        #region Set Method Tests

        [TestMethod]
        public void SharedHashset_SetEquals_CollectionOfReferenceTypes_TestCollectionEqualsToStoredData_TrueReturned()
        {
            using (var sharedHashset = new SharedHashset<TestObject>(TEST_COLLECTION_NAME))
            {
                var testCollection = Builder<TestObject>.CreateListOfSize(10).Build();

                //add data in the wrong order
                sharedHashset.AddRange(testCollection.Skip(5));
                sharedHashset.AddRange(testCollection.Take(5));

                sharedHashset.SetEquals(testCollection).Should().BeTrue();
            }
        }

        [TestMethod]
        public void SharedHashset_SetEquals_CollectionOfReferenceTypes_TestCollectionNotEqualsToStoredData_FalseReturned()
        {
            using (var sharedHashset = new SharedHashset<TestObject>(TEST_COLLECTION_NAME))
            {
                var testCollection = Builder<TestObject>.CreateListOfSize(10).Build();

                //add data in the wrong order
                sharedHashset.AddRange(testCollection.Skip(7));
                sharedHashset.AddRange(testCollection.Take(5));

                sharedHashset.SetEquals(testCollection).Should().BeFalse();
            }
        }

        [TestMethod]
        public void SharedHashset_SetEquals_CollectionOfValueTypes_TestCollectionEqualsToStoredData_TrueReturned()
        {
            using (var sharedHashset = new SharedHashset<int>(TEST_COLLECTION_NAME))
            {
                var collection1 = new int[] { 11, 22, 33, 44, 55 };
                var collection2 = new int[] { 33, 44, 55, 66, 77 };

                var testCollection = collection1.Union(collection2);

                sharedHashset.AddRange(collection2);
                sharedHashset.AddRange(collection1);

                sharedHashset.SetEquals(testCollection).Should().BeTrue();
            }
        }

        [TestMethod]
        public void SharedHashset_SetEquals_CollectionOfValueTypes_TestCollectionNotEqualsToStoredData_FalseReturned()
        {
            using (var sharedHashset = new SharedHashset<int>(TEST_COLLECTION_NAME))
            {
                var collection1 = new int[] { 11, 22, 33, 44, 55 };
                var collection2 = new int[] { 33, 44, 55, 66, 77 };

                var testCollection = collection1.Skip(1).Union(collection2);

                sharedHashset.AddRange(collection2);
                sharedHashset.AddRange(collection1);

                sharedHashset.SetEquals(testCollection).Should().BeFalse();
            }
        }

        #endregion

        #region Misc Tests

        [TestMethod]
        public void SharedHashset_ShouldEnlargeMemoryMappedFileCapacityIfNeeded()
        {
            //new collection with very small reserved capacity of memory mapped file (1 byte) --> will have to expand on first add call
            using (var sharedHashset = new SharedHashset<TestObject>(TEST_COLLECTION_NAME,EqualityComparer<TestObject>.Default,true,0,1))
            {
                var expectedCollection = Builder<TestObject>.CreateListOfSize(10).Build();
                sharedHashset.Invoking(hashset => hashset.AddRange(expectedCollection))
                             .ShouldNotThrow<Exception>();
            }
        }

        #endregion
    }
}
